#include <stdio.h>
#include <string.h>
#include <mkindex/wordPages.h>

#ifdef  UNIT_PAGE_ARRAY
#undef  UNIT_PAGE_ARRAY
#endif
/** Page array incremental size */
#define UNIT_PAGE_ARRAY   50

/** Word & its page link struct */
struct wordPages {
  char *word;
  /** page array */
  char **page;
  /** page array size allocation */
  unsigned int pageN;
  /** length of page array */
  unsigned int lenPage;   /* "= 0" gcc doesn't allow assignment in struct.. */
};

/** wordPages function data struct */
struct wordPagesFnData {
  /** Contain the value of sizeof(struct wordPages) */
  unsigned char sizeof_wordPages;
  /** Contain the value of sizeof(char **) */
  unsigned char sizeof_charP;
  /** Type <code>strFn</code> */
  strFn strFn;
  /** If type <code>strFn</code> was pass in and 
    not created by library's function. */
  boolean setStrFn;
};

/*======= declare internal function */
wordPages wordPages_create(wordPagesFn fn, const char *word, 
    const char *aPage);
wordPages wordPages_createWith(wordPagesFn fn, const char *word, 
    const unsigned int wordLen, const char *aPage);
boolean wordPages_setStrFn(wordPagesFn fn, strFn strFn);
boolean wordPages_free(void **aWordPagesP);
boolean wordPages_freeFn(wordPagesFn *fnP);
/* specific operation */
boolean wordPages_setWordPages(wordPagesFn fn, wordPages aWordPages, 
    const char *word, const char *aPage);
boolean wordPages_setWordPagesWith(wordPagesFn fn, wordPages aWordPages, 
    const char *word, const unsigned int wordLen, const char *aPage);
char *wordPages_getWord(const wordPages aWordPages);
char *wordPages_getWordClone(wordPagesFn fn, 
    const wordPages aWordPages);
char **wordPages_getPages(
    const wordPages aWordPages, unsigned int *pagesLen);
char **wordPages_getPagesClone(wordPagesFn fn, 
    const wordPages aWordPages, unsigned int *pagesLen);
int wordPages_getPagesLen(const wordPages aWordPages);
char *wordPages_get1stPage(const wordPages aWordPages);
char *wordPages_get1stPageClone(wordPagesFn fn, 
    const wordPages aWordPages);
boolean wordPages_isWord(const wordPages aWordPages, const char *word);
int wordPages_compare(const char *word, const wordPages aWordPages);
g_reply wordPages_compareWordPages(const void *aWordPages1, 
    const void *aWordPages2);
wpState wordPages_addPage(wordPagesFn fn, 
    wordPages aWordPages, const char *aPage);
/*--- function ONLY for internal use ---*/
boolean wordPages_initStrFn(wordPagesFn fn);

/*======= directly exposed function */
/** Create a <code>wordPagesFn</code> that 
provide wordPages function pointers.
@return a working struct of <code>wordPages</code> function pointers.
*/
wordPagesFn wordPages_createFn(void) {
  wordPagesFn fn;
  FN_MALLOC_ERRMSG(fn, sizeof(struct wordPagesFn), 
      "Error: wordPages_createFn(void) malloc(fn)")
  if (fn == NULL) {return NULL;}
  FN_MALLOC_ERRMSG(fn->d, sizeof(struct wordPagesFnData), 
      "Error: wordPages_createFn(void) malloc(fn->d)")
  if (fn->d == NULL) {wordPages_freeFn(&fn); return NULL;}
  /*--- data ---*/
  fn->d->sizeof_wordPages = sizeof(struct wordPages);
  fn->d->sizeof_charP = sizeof(char *);
  fn->d->strFn = NULL;
  fn->d->setStrFn = false;
  /*--- init function pointers ---*/
  fn->create = wordPages_create;
  fn->createWith = wordPages_createWith;
  fn->setStrFn = wordPages_setStrFn;
  fn->free = wordPages_free;
  fn->freeFn = wordPages_freeFn;
  /* specific operation */
  fn->setWordPages = wordPages_setWordPages;
  fn->setWordPagesWith = wordPages_setWordPagesWith;
  fn->getWord = wordPages_getWord;
  fn->getWordClone = wordPages_getWordClone;
  fn->getPages = wordPages_getPages;
  fn->getPagesClone = wordPages_getPagesClone;
  fn->getPagesLen = wordPages_getPagesLen;
  fn->get1stPage = wordPages_get1stPage;
  fn->get1stPageClone = wordPages_get1stPageClone;
  fn->isWord = wordPages_isWord;
  fn->compare = wordPages_compare;
  fn->compareWordPages = wordPages_compareWordPages;
  fn->addPage = wordPages_addPage;
  return fn;
}

/*======= exposed through function pointers */
/** Create a wordPages with this word.
Will create another copy of this word (clone of), 
will NOT use the passed in string.
@param fn the <code>wordPagesFn</code> to use.
@param word pass in the word.
@param aPage the page number to add.
@return created wordPages.
*/
wordPages wordPages_create(wordPagesFn fn, const char *word, 
    const char *aPage) {
  if (word == NULL) {
    return wordPages_createWith(fn, NULL, 0, aPage);
  }
  return wordPages_createWith(fn, word, strlen(word), aPage);
}

/** Create a wordPages with this word and this word has this length.
Will create another copy of this word (clone of), 
Will create another copy of this page (clone of), 
will NOT use the passed in string.
Allows creation of an empty <code>wordPages</code> 
for possible increase in processing speed,
with <code>word</code> value of <code>NULL</code> and 
with <code>aPage</code> value of <code>NULL</code>.
@param fn the <code>wordPagesFn</code> to use.
@param word pass in the word.
@param wordLen the length of the <code>word</code>.
@param aPage the page number to add.
@return a created wordPages or <code>NULL</code> if failure.
*/
wordPages wordPages_createWith(wordPagesFn fn, const char *word, 
    const unsigned int wordLen, const char *aPage) {
  if (fn == NULL || fn->d == NULL || wordLen < 0) {
    return NULL;
  }
  wordPages aWordPages;
  FN_MALLOC_ERRMSG(aWordPages, fn->d->sizeof_wordPages, 
      "Error: wordPages_createWith(..) malloc(aWordPages)")
  if (aWordPages == NULL) {return NULL;}
  /* init data */
  if (fn->d->strFn == NULL) {
    if (wordPages_initStrFn(fn) == false) {
      perror("Error: wordPages_createWith(..) wordPages_initStrFn(fn) == false");
      wordPages_free((void *) &aWordPages);
      return NULL;
    }
  }
  /* do NOT point to the external string! Encapsulate & create another. */
  if (word == NULL) {
    aWordPages->word = NULL;
  } else {
    aWordPages->word = fn->d->strFn->cloneLen(fn->d->strFn, word, wordLen);
  }
  aWordPages->pageN = UNIT_PAGE_ARRAY;
  FN_MALLOC_ERRMSG(aWordPages->page, aWordPages->pageN * fn->d->sizeof_charP, 
      "Error: wordPages_createWith(..) malloc(aWordPages->page)")
  if (aWordPages->page == NULL) {
    wordPages_free((void *) &aWordPages); 
    return NULL;
  }
  if (aPage == NULL) {
    aWordPages->lenPage = 0;
    aWordPages->page[0] = NULL;
  } else {
    aWordPages->lenPage = 1;
    aWordPages->page[0] = fn->d->strFn->clone(fn->d->strFn, aPage);
  }
  return aWordPages;
}

/** Set <code>wordPagesFn</code>'s <code>strFn</code>.
If <code>strFn</code> is already created, 
this action will be ignored and <code>false</code> will be returned.
<code>wordPagesFn</code> will NOT free the memory of 
<code>strFn</code> that is created externally, 
you MUST free this yourself.
@param fn the <code>wordPagesFn</code> to use.
@param strFn pass in the <code>strFn</code> to use.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean wordPages_setStrFn(wordPagesFn fn, strFn strFn) {
  if (fn == NULL || fn->d == NULL || strFn == NULL) {
    return false;
  }
  fn->d->strFn = strFn;
  fn->d->setStrFn = true;
  return true;
}

/** Free a wordPages, free up the memory.
Use <code>void</code> to provide customizable function 
for other library, so they can free memory of similar struct 
through commond function call.
i.e. it is NOT declared as 
"<code>void (*free)(wordPages *aWordPagesP);</code>", 
or NOT using type <code>wordPages</code>.
@param aWordPagesP pass in the address of <code>wordPages</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean wordPages_free(void **aWordPagesP) {
  /* NOT critical if wordPages is NULL, assume all's fine */
  if (*aWordPagesP != NULL) {
    /* free the stored word string */
    free(((wordPages) *aWordPagesP)->word);
    ((wordPages) *aWordPagesP)->word = NULL;
    /* free the stored page array & its' individual pages 
      NB: can use array2D->free2DArray(..) but "wordPages" 
      struct will need to have reference to array2D .. */
    while (((wordPages) *aWordPagesP)->lenPage > 0) {
      /* free the each page string */
      ((wordPages) *aWordPagesP)->lenPage--;
      free(((wordPages) *aWordPagesP)->page[
          ((wordPages) *aWordPagesP)->lenPage]);
    }
    /* free the stored page array */
    free(((wordPages) *aWordPagesP)->page);
    ((wordPages) *aWordPagesP)->page = NULL;
    /* free the wordPages struct */
    free((wordPages) *aWordPagesP);
    *aWordPagesP = NULL;
  }
  return true;
}

/** Free a wordPagesFn, free up the memory.
@param fnP pass in the address of <code>wordPagesFn</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean wordPages_freeFn(wordPagesFn *fnP) {
  /* recoverable error */
  if (*fnP == NULL) {return true;}
  if ((*fnP)->d != NULL) {
    /* free strFn ONLY if it is created within library's function */
    if ((*fnP)->d->setStrFn == false
        && (*fnP)->d->strFn != NULL
        && (*fnP)->d->strFn->freeFn(&(*fnP)->d->strFn) == false) {
      /* possible memory leak */
      perror("Error: wordPages_freeFn(wordPagesFn) ..(*fnP)->d->strFn->freeFn(&(*fnP)->d->strFn)");
    }
    /* free wordPagesFnData struct */
    free((*fnP)->d);
    (*fnP)->d = NULL;
  }
  /* free wordPagesFn struct */
  free(*fnP);
  *fnP = NULL;
  return true;
}

/** Set properties of the <code>wordPages</code>, 
function provide for possible increase in processing speed.
The page is NOT added but will overide pointer reference 
of the 1st page in the list or array.
Will free memory of previous <code>word</code> and 
previous 1st page in the list or array.
Will store a cloned copy of the word & the page passed in
NOT the original.
@param fn the <code>wordPagesFn</code> to use.
@param aWordPages pass in the wordPages.
@param word pass in the word.
@param aPage the page number.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean wordPages_setWordPages(wordPagesFn fn, wordPages aWordPages, 
    const char *word, const char *aPage) {
  return wordPages_setWordPagesWith(fn, 
      aWordPages, word, strlen(word), aPage);
}

/** Set properties of the <code>wordPages</code>, 
function provide for possible increase in processing speed.
The page is NOT added but will overide pointer reference 
of the 1st page in the list or array.
Will free memory of previous <code>word</code> and 
previous 1st page in the list or array.
Will store a cloned copy of the word & the page passed in
NOT the original.
@param fn the <code>wordPagesFn</code> to use.
@param aWordPages pass in the wordPages.
@param word pass in the word.
@param wordLen the length of the <code>word</code>.
@param aPage the page number.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean wordPages_setWordPagesWith(wordPagesFn fn, wordPages aWordPages, 
    const char *word, const unsigned int wordLen, const char *aPage) {
  if (fn == NULL || fn->d == NULL || aWordPages == NULL 
      || word == NULL || wordLen < 0 || aPage == NULL) {
    return false;
  }
  if (fn->d->strFn == NULL) {
    if (wordPages_initStrFn(fn) == false) {
      perror("Error: wordPages_setWordPagesWith(..) wordPages_initStrFn(fn) == false");
      return false;
    }
  }
  /* free previous word */
  if (aWordPages->word != NULL) {
    free(aWordPages->word);
  }
  aWordPages->word = fn->d->strFn->cloneLen(fn->d->strFn, word, wordLen);
  /* free previous 1st page */
  if (aWordPages->page[0] == NULL) {
    /* we have the 1st page */
    if (aWordPages->lenPage <= 0) {
      aWordPages->lenPage++;
    }
  } else {
    free(aWordPages->page[0]);
  }
  aWordPages->page[0] = fn->d->strFn->clone(fn->d->strFn, aPage);
  return true;
}

/** Get the word from this wordPages, 
function provide for possible increase in processing speed. 
This is not a cloned string but 
what is inside this <code>wordPages</code>.
WARNING: <code>wordPages</code> will free its own memory 
if it still have reference to any of its data.
Know what you're doing, misuse of function could lead to program crashes.
@param aWordPages pass in the wordPages.
@return the word, or <code>NULL</code> if failure.
*/
char *wordPages_getWord(const wordPages aWordPages) {
  if (aWordPages == NULL) {return NULL;}
  return aWordPages->word;
}

/** Get a copy of the word (clone) from this wordPages.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>wordPagesFn</code> to use.
@param aWordPages pass in the wordPages.
@return the word, or <code>NULL</code> if failure.
*/
char *wordPages_getWordClone(wordPagesFn fn, 
    const wordPages aWordPages) {
  if (fn == NULL || fn->d == NULL || aWordPages == NULL) {return NULL;}
  if (fn->d->strFn == NULL) {
    if (wordPages_initStrFn(fn) == false) {
      perror("Error: wordPages_getWordClone(..) wordPages_initStrFn(fn) == false");
      return NULL;
    }
  }
  return fn->d->strFn->clone(fn->d->strFn, aWordPages->word);
}

/** Get a copy of the pages from this wordPages,
function provide for possible increase in processing speed. 
This is not a cloned string but 
what is inside this <code>wordPages</code>.
WARNING: <code>wordPages</code> will free its own memory 
if it still have reference to any of its data.
Know what you're doing, misuse of function could lead to program crashes.
@param aWordPages pass in the wordPages.
@param pagesLen the length of the duplicated pages array.
@return pointer to the pages array within this wordPages, 
  the returned array is cloned, or <code>NULL</code> if failure.
*/
char **wordPages_getPages(
    const wordPages aWordPages, unsigned int *pagesLen) {
  if (aWordPages == NULL || aWordPages->lenPage <= 0) {
    *pagesLen = 0;
    return NULL;
  }
  *pagesLen = aWordPages->lenPage;
  return aWordPages->page;
}

/** Get a copy of the pages from this wordPages.
<p>Since this 2 dimension array 
is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>wordPagesFn</code> to use.
@param aWordPages pass in the wordPages.
@param pagesLen the length of the duplicated pages array.
@return pointer to the pages array within this wordPages, 
  the returned array is cloned, or <code>NULL</code> if failure.
*/
char **wordPages_getPagesClone(wordPagesFn fn, 
    const wordPages aWordPages, unsigned int *pagesLen) {
  if (fn == NULL || fn->d == NULL || aWordPages == NULL
      || aWordPages->lenPage <= 0) {
    *pagesLen = 0;
    return NULL;
  }
  /* do NOT pass out internal details! Encapsulate & create another. */
  char **page;
  FN_MALLOC_ERRMSG(page, aWordPages->lenPage * fn->d->sizeof_charP, 
      "Error: wordPages_getPagesClone(..) malloc(page)")
  if (page == NULL) {*pagesLen = 0; return NULL;}
  if (fn->d->strFn == NULL) {
    if (wordPages_initStrFn(fn) == false) {
      perror("Error: wordPages_getPagesClone(..) wordPages_initStrFn(fn) == false");
      return NULL;
    }
  }
  *pagesLen = aWordPages->lenPage;
  do {
    (*pagesLen)--;
    page[*pagesLen] = 
        fn->d->strFn->clone(fn->d->strFn, aWordPages->page[*pagesLen]);
  } while (*pagesLen > 0);
  *pagesLen = aWordPages->lenPage;
  return page;
}

/** Get the total number of pages stored within this wordPages.
@param aWordPages pass in the wordPages.
@return the number of pages from this wordPages or 
  -1 if failure occurs.
*/
int wordPages_getPagesLen(const wordPages aWordPages) {
  if (aWordPages == NULL) {return -1;}
  return aWordPages->lenPage;
}

/** Get only the 1st page within this wordPages.
function provide for possible increase in processing speed. 
This is not a cloned string but 
what is inside this <code>wordPages</code>.
WARNING: <code>wordPages</code> will free its own memory 
if it still have reference to any of its data.
Know what you're doing, misuse of function could lead to program crashes.
@param aWordPages pass in the wordPages.
@return Return this page's page number or <code>NULL</code> if failure.
*/
char *wordPages_get1stPage(const wordPages aWordPages) {
  if (aWordPages == NULL || aWordPages->lenPage == 0) {return NULL;}
  return aWordPages->page[0];
}

/** Get only the 1st page (a cloned copy) within this wordPages.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>wordPagesFn</code> to use.
@param aWordPages pass in the wordPages.
@return Return this page's page number or <code>NULL</code> if failure.
*/
char *wordPages_get1stPageClone(wordPagesFn fn, 
    const wordPages aWordPages) {
  if (fn == NULL || fn->d == NULL || aWordPages == NULL 
      || aWordPages->lenPage == 0) {
    return NULL;
  }
  if (fn->d->strFn == NULL) {
    if (wordPages_initStrFn(fn) == false) {
      perror("Error: wordPages_get1stPageClone(..) wordPages_initStrFn(fn) == false");
      return NULL;
    }
  }
  /* return clone */
  return fn->d->strFn->clone(fn->d->strFn, aWordPages->page[0]);
}

/** Is word the same as in this wordPages.
Case sensitive and white space is also compared!
<p>
Uses standard <code>int strcmp(const char *str1, const char *str2);</code>
for comparison.
<code>str1</code> is word taken from <code>aWordPages</code>
<code>str2</code> is the string <code>word</code>
Returns zero if <code>str1</code> and <code>str2</code> are equal. 
  Returns less than zero or greater than zero 
  if <code>str1</code> is less than or 
  greater than <code>str2</code> respectively.
</p>
@param aWordPages pass in the wordPages.
@param word pass in the word.
@return true if same, otherwise false.
*/
boolean wordPages_isWord(const wordPages aWordPages, const char *word) {
  if (aWordPages != NULL && strcmp(aWordPages->word, word) == 0) {
    return true;
  }
  return false;
}

/** Compare word in these wordPages.
Case sensitive and white space is also compared!
<p>
Uses standard <code>int strcmp(const char *str1, const char *str2);</code>
for comparison.
<code>str1</code> is the <code>word</code>.
<code>str2</code> is word taken from <code>aWordPages</code>.
Returns zero if <code>str1</code> and <code>str2</code> are equal. 
  Returns less than zero or greater than zero 
  if <code>str1</code> is less than or 
  greater than <code>str2</code> respectively.
</p>
@param word pass in the word.
@param aWordPages pass in the wordPages.
@return Returns zero if <code>word</code> and <code>aWordPages</code> are equal. 
  Returns less than zero or greater than zero 
  if <code>word</code> is less than or 
  greater than <code>aWordPages</code> respectively.
*/
int wordPages_compare(const char *word, const wordPages aWordPages) {
  return strcmp(word, aWordPages->word);
}

/** Compare word in these wordPages.
Case sensitive and white space is also compared!
<p>Uses standard 
<code>int strcmp(const char *str1, const char *str2);</code>
for comparison.
<code>str1</code> is word taken from <code>aWordPages1</code>.
<code>str2</code> is word taken from <code>aWordPages2</code>.
</p>
@param aWordPages1 pass in the wordPages as <code>void *</code>.
@param aWordPages2 pass in the wordPages as <code>void *</code>.
@return Returns <code>g_replyEqual</code> if 
  <code>str1</code> and <code>str2</code> are equal. 
  Returns <code>g_replyLesser</code> or <code>g_replyGreater</code>
  if <code>str1</code> is less than or 
  greater than <code>str2</code> respectively.
  Otherwise <code>g_replyFail</code> for failure.
*/
g_reply wordPages_compareWordPages(const void *aWordPages1, 
    const void *aWordPages2) {
  if (aWordPages1 == NULL || aWordPages2 == NULL) {return g_replyFail;}
  int compare = strcmp(((wordPages) aWordPages1)->word, 
      ((wordPages) aWordPages2)->word);
  if (compare < 0) {
    return g_replyLesser;
  } else if (compare > 0) {
    return g_replyGreater;
  }
  return g_replyEqual;
}

/** Add a page into this wordPages. 
If page already exist, this page will be ignored (no duplicates), success is return.
Will create another copy of this page (clone of), 
will NOT use the passed in string.
@param fn the <code>wordPagesFn</code> to use.
@param aWordPages pass in the wordPages.
@param aPage the page number to add.
@return <code>wpSuccess</code> if successful, <code>wpFail</code> for failure 
  or <code>wpIgnored</code> if <code>page</code> already exist.
*/
wpState wordPages_addPage(wordPagesFn fn, 
    wordPages aWordPages, const char *aPage) {
  if (fn == NULL || aWordPages == NULL || aPage == NULL) {
    return wpFail;
  }
  unsigned int i;
  for (i = 0; i < aWordPages->lenPage; i++) {
    /* already exist? */
    if (strcmp(aPage, aWordPages->page[i]) == 0) {return wpIgnored;}
  }
  /* need more memory? */
  if (i > aWordPages->pageN) {
    char **tmpPage;
    /* allocate more memory */
    aWordPages->pageN += UNIT_PAGE_ARRAY;
    tmpPage = realloc(aWordPages->page, 
        aWordPages->pageN * fn->d->sizeof_charP);
    /* unsuccessful */
    if (tmpPage == NULL) {return wpFail;}
    aWordPages->page = tmpPage;
  }
  if (fn->d->strFn == NULL) {
    if (wordPages_initStrFn(fn) == false) {
      perror("Error: wordPages_addPage(..) wordPages_initStrFn(fn) == false");
      return wpFail;
    }
  }
  /* insert */
  aWordPages->page[i] = fn->d->strFn->clone(fn->d->strFn, aPage);
  aWordPages->lenPage++;
  return wpSuccess;
}

/*======= function ONLY for internal use */
/** Initialize <code>wordPagesFn</code>'s <code>strFn</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param fn the <code>wordPagesFn</code> to use.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean wordPages_initStrFn(wordPagesFn fn) {
  fn->d->strFn = ystr_createFn();
  if (fn->d->strFn == NULL) {
    perror("Error: wordPages_initStrFn(wordPagesFn) fn->d->strFn == NULL");
    return false;
  }
  return true;
}
